/*
This file is part of [ahttpserver] project. 

Author: Artem Kustikov (kustikoff[at]tut.by)

This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.


-- fastcgi.hpp --

Code based on following document:

		FastCGI Specification
		Mark R. Brown
		Open Market, Inc.

		Document Version: 1.0
		29 April 1996

Some code logic was copied from Open Market FastCGI C++ library (fcgi-2.4.0).
To avoid licensing issues attached original license file - "LICENSE.TERMS".
*/

#include <limits>

#include "aconnect/types.hpp"
#include "aconnect/error.hpp"
#include "aconnect/complex_types.hpp"


#ifndef FASTCGI_DETAILS_HPP
#define FASTCGI_DETAILS_HPP

namespace fastcgi
{
	typedef unsigned char byte_type;

	// Listening socket file number
	const int ListenSockFileno = 0;

	const size_t MaxMessageContentLength  = 0xffff;

	aconnect::string_constant FcgiWebServersEnvVariable = "FCGI_WEB_SERVER_ADDRS";
	
	// Number of bytes in a FCGI_Header.  Future versions of the protocol
	// will not reduce this number.
	const int HeaderLenght = 8;

	// Value for version component of FCGI Header
	const byte_type FcgiVersion1 = 1;

	// Value for requestId component of FCGI Header
	const byte_type FcgiNullRequestId = 0;

	// Values for type component of FCGI Header
	namespace FcgiMessage
	{
		enum Type
		{
			BeginGequest = 1,
			AbortRequest = 2,
			EndRequest   = 3,
			Params        = 4,
			Stdin         = 5,
			Stdout        = 6,
			Stderr        = 7,
			Data          = 8,
			GetValues    = 9,
			GetValuesResult = 10,
			UnknownType      = 11,
			Maxtype  = UnknownType
		};
	}
	

	struct Header
	{
		byte_type version;
		byte_type type;
		byte_type requestIdB1;
		byte_type requestIdB0;
		byte_type contentLengthB1;
		byte_type contentLengthB0;
		byte_type paddingLength;
		byte_type reserved;
	
	};

	struct BeginRequestBody
	{
		byte_type roleB1;
		byte_type roleB0;
		byte_type flags;
		byte_type reserved[5];
	};

	struct BeginRequestRecord
	{
		Header header;
		BeginRequestBody body;

	};

	// Mask for flags component of FCGI BeginRequestBody
	namespace FcgiBeginRequestFlags
	{
		const byte_type Default = 0;
		const byte_type KeepConnection = 1;
	}
	
	// Values for role component of FCGI BeginRequestBody
	namespace ApplicationRole
	{
		const int Responder = 1;
		const int Authorizer = 2;
		const int Filter = 3;

	}
	
	struct EndRequestBody
	{
		byte_type appStatusB3;
		byte_type appStatusB2;
		byte_type appStatusB1;
		byte_type appStatusB0;
		byte_type protocolStatus;
		byte_type reserved[3];
	
	} ;

	// Values for protocolStatus component of FCGI EndRequestBody
	namespace FcgiProtocolStatus
	{
		const byte_type RequestComplete	= 0;
		const byte_type CantMultiplexConnection	= 1;
		const byte_type Overloaded = 2;
		const byte_type UnknownRole = 3;
	};

	struct EndRequestRecord
	{
		Header header;
		EndRequestBody body;
	
	};


	// Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
	namespace FcgiApplicationProperties
	{
		const char FcgiMaxConns[]		= "FCGI_MAX_CONNS";
		const char FcgiMaxReqs[]		= "FCGI_MAX_REQS";
		const char FcgiMpxsConns[]		= "FCGI_MPXS_CONNS";
	}

	struct FcgiApplicationInfo
	{
		int MaxConnsCount;
		int MaxReqsCount;
		bool CanMultiplexConns;

		FcgiApplicationInfo() :
			MaxConnsCount (1),
			MaxReqsCount (1),
			CanMultiplexConns (false) {
		}
	
	};

	struct UnknownTypeBody
	{
		byte_type type;    
		byte_type reserved[7];
	
	};

	struct UnknownTypeRecord
	{
		Header header;
		UnknownTypeBody body;
	
	};

	// ahttpserve defines

	namespace FcgiError
	{
		const aconnect::err_type SystemError = -1;
		const aconnect::err_type ProtocolError = -2;
		const aconnect::err_type InvalidMessageType = -3;
		const aconnect::err_type FastcgiInvalidVersion = -4;
		const aconnect::err_type ClientConnectionFailed = -5;
		const aconnect::err_type ApplicationOverload = -6;
		const aconnect::err_type InvalidConnectionInfo = -7;
		const aconnect::err_type ReadFailed = -8;
		const aconnect::err_type WriteFailed = -9;

	}

	struct fastcgi_error : public aconnect::application_error
	{
		fastcgi_error (aconnect::err_type errorCode, const char* format, ...) : aconnect::application_error ("") {
			
			_errorCode = errorCode;

			if (0 == format) {
				_message = "FastCGI operation failed";

			} else {
				FORMAT_VA_MESSAGE(format, message);
				_message.swap (message);
			}

			if (errorCode != 0)
				_message += (" " + formatErrorMessage (errorCode));
		}
		
		static aconnect::string formatErrorMessage (aconnect::err_type errorCode);
		
		virtual ~fastcgi_error() throw () { }

		virtual const char * what() const throw () {	
			return (_message.c_str());
		}

		const aconnect::err_type code() const throw () {	
			return _errorCode;
		}
	private:
		std::string _message;
		aconnect::err_type _errorCode;
	};


	namespace support
	{
		void formatFcgiHeader (Header& header, byte_type type, int requestId, int contentLength = 0, byte_type paddingLength = 0);
		void formatBeginRequestBody (BeginRequestBody &body, int role, bool keepConnection);
		
		size_t getKeyValuePairLength (const char* name, const char* value = 0);
		int writeKeyValuePair (byte_type* buff, int buffSize, const char* name, const char* value = 0);

		aconnect::str2str_map parseKeyValuePairs (const byte_type* buff, size_t length) throw (fastcgi_error);
		aconnect::string formatKeyValuePairs (aconnect::str2str_map pairs);
	}
}
#endif	// FASTCGI_DETAILS_HPP

